"""This package contains the per-database adapapters"""

from contextlib import closing, contextmanager
from random import randint
import sys

from sqlalchemy import create_engine


class MigrationOptions(object):
    
    def __init__(self, **vargs):
        self.__dict__.update(vargs)


class BaseDataSource(object):
    
    show_database_sql = None
    show_tables_sql = None # must be defined in descendant
    select_applied_revisions_sql =  None # must be defined in descendant
    insert_revision_sql =  None # must be defined in descendant
    delete_revision_sql = None # must be defined in descendant
    initial_migration = None # must be defined in descendant
    create_database_sql = """CREATE DATABASE "%(db)s" """
    drop_database_sql = """DROP DATABASE "%(db)s" """

    def __init__(self):
        self.expansions = {'scheme': self.scheme,
                           'id_col': 'id',
                           'migration_package_col': 'package',
                           'schema_table': 'applied_revision',
                           'versiontable': 'applied_revision', # so the published examples work
                           'schema_revision_col': 'revision',
                           }
        self.expansions.update(self.dbi_placeholders())
    
    def connect(self):
        self.admin_conn = Connection(self, self.admin_uri)
        self.migration_conn = Connection(self, self.migration_uri)
        self.app_conn = Connection(self, self.app_uri)
        return self
    
    def close(self):
        self.admin_conn.close()
        self.migration_conn.close()
        self.app_conn.close()
        
    def dbi_placeholders(self):
        holder = self.dbi_placeholder()
        return dict(revision=holder, package=holder)

    def has_schema_table(self):
        if not self.has_database():
            return False
        rows = list(self.migration_conn.execute(self.show_tables_sql).fetchall())
        return len(rows) == 1
    
    def has_database(self):
        if self.show_database_sql:
            rows = list(self.admin_conn.execute(self.show_database_sql).fetchall())
            if len(rows) == 0:
                return False
        return True
    
    def read_params(self, params):
        self.read_command(self, **MigrationOptions(params))

    def create_database_if_needed(self):
        if not self.has_database():
            self.create_database()

    def drop_database_if_needed(self):
        if self.has_database():
            self.close()
            self.drop_database()

    def create_database(self):
        self.admin_conn.execute(self.create_database_sql)

    def drop_database(self):
        self.admin_conn.execute(self.drop_database_sql)
        
    def get_applied_revisions(self):
        rows = self.migration_conn.execute(self.select_applied_revisions_sql).fetchall()
        return sorted([int(x[0]) for x in rows])

    def record_revision_applied(self, revision, name):
        """This revision was applied"""
        self.migration_conn.execute(self.insert_revision_sql , name, revision)

    def record_revision_unapplied(self, revision, name):
        """This revision was unapplied"""
        if revision == 1:
            return
        self.migration_conn.execute(self.delete_revision_sql, revision)

    def update(self, additional_expansions):
        self.expansions.update(additional_expansions)
        
    @classmethod
    def from_options(cls, options, args):
        self = cls()
        self.options = options
        self.args = args
        self.read_options(options, args)
        return self

    @classmethod
    def from_command(cls, cmd):
        self = cls()
        self.read_command(cmd)
        return self

    @classmethod
    def from_params(cls, cmd):
        self = cls()
        self.read_params(cmd)
        return self

    @classmethod
    def from_config_file(cls, config):
        self = cls()
        self.read_config_file(config)
        return self

    def expansionFromCommand(self, cmd, field, default=None):
        if hasattr(cmd, field):
            value = getattr(cmd, field)
            self.expansions[field] = default if value is None else value
    
    def expansionFromIniOption(self, config, field, default=None):
        self.expansions[field] = self.getIniOption(config, field, default)

    def getIniOption(self, config, name, default):
        if config.has_option('dbmigrate', name):
            return config.get('dbmigrate', name)
        else:
            return default

    def expansionFromArg(self, args, field, j, default):
        self.expansions[field] = args[j] if len(args) > j else default

    def expansionFromRandomString(self, name, size, suffix):
        self.expansions[name] = self.randomString(size) + suffix

    @classmethod
    def randomString(cls, size):
        return ''.join(list(cls.randomCharacters(size)))

    @staticmethod
    def randomCharacters(size):
        for _x in range(0, size):
            yield chr(randint(65, 65+25)).lower()

        
class Connection(object):
    
    def __init__(self, context, db_uri_func):
        self.uri_func = db_uri_func
        self._engine = None
        self._connection = None
        self.context = context

    def _get_engine(self):
        if not self._engine:
            self._engine = create_engine(self.uri_func())
            self._engine.connect()
        return self._engine
    
    engine = property(_get_engine)

    def _get_connection(self):
        if not self._connection:
            self._connection = self.engine.connect()
        return self._connection
    
    connection = property(_get_connection)
    
    def close(self):
        if self._connection:
            self._connection.close()

    expansions = property(lambda self: self.context.expansions)
    
    def execute(self, sql, *pargs, **vargs):
        return self.connection.execute(sql % self.expansions, *pargs, **vargs)
    
    @contextmanager
    def manage_transaction(self, revision=""):
        """Manage a database transaction.

        Usage::

            with self.manage_transaction():
                ...

        """
        if revision == "1" or revision == 1:
            yield
        else:
            self.transaction = self.connection.begin()
            try:
                yield
                self.transaction.commit()
            except:
                self.transaction.rollback()
                raise    

    @contextmanager
    def raw_cursor(self):
        raw_conn = self.engine.raw_connection()
        raw_conn.set_isolation_level(0)
        try:
            yield raw_conn.cursor()
        finally:
            raw_conn.commit()
            raw_conn.close()
